using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace WindowsGame2
{

    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        HUD hud;

        RotatedRectangle ball;
        //float ballScaleX, ballScaleY;
        float ballSpeed = 10.0f;

        Player player1, player2;

        Ray ray;

        Vector2 ballVelocity;
        //Vector2 ballPosition;

        Color[] player1TextureData;
        //Color[] player2TextureData;
        Color[] ballTextureData;

        bool collisionDetected = false;

        KeyboardState oldState, state;
        GamePadState gamePadState, oldGamePadState;
        Vector2 rightThumbStick;
        const float THUMBSTICK_DEAD_ZONE = 0.26f;


        //Contact points are cleared and re-added each update
        List<Vector2> contactPoints = new List<Vector2>();

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);

            // Use multi-sampling to smooth corners of objects  
            graphics.PreferMultiSampling = true;
            
            // Set back buffer resolution  
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 720;
            
            IsFixedTimeStep = true;
            
            this.IsMouseVisible = true;

            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            //this should not be here, find a good looking solution
            Assets.Load(Content);

            player1 = new Player(new Rectangle(32, 
                                (GraphicsDevice.Viewport.Bounds.Height / 2) - (Assets.Paddle.Height / 2),
                                 Assets.Paddle.Width, 
                                 Assets.Paddle.Height), MathHelper.ToRadians(45));

            player2 = new Player(new Rectangle(GraphicsDevice.Viewport.Bounds.Width - (Assets.Paddle.Width * 3),
                                (GraphicsDevice.Viewport.Bounds.Height / 2) - (Assets.Paddle.Height / 2),
                                 Assets.Paddle.Width,
                                 Assets.Paddle.Height), MathHelper.ToRadians(45));

            ball = new RotatedRectangle(new Rectangle((GraphicsDevice.Viewport.Bounds.Width / 2) - (Assets.Ball.Width / 2),
                                 (GraphicsDevice.Viewport.Bounds.Height / 2) - (Assets.Ball.Height / 2),
                                 Assets.Ball.Width, Assets.Ball.Height), 0.0f);

            ballVelocity = Vector2.Zero;
           // ballPosition = new Vector2(ball.X, ball.Y);

            hud = new HUD();

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            player1TextureData = new Color[Assets.Paddle.Width * Assets.Paddle.Height];
            Assets.Paddle.GetData(player1TextureData);

            ballTextureData = new Color[Assets.Ball.Width * Assets.Ball.Height];
            Assets.Ball.GetData(ballTextureData);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            HandleInput(gameTime);

            //update players
            player1.Update(gameTime, GraphicsDevice.Viewport.Bounds.Height);
            player2.Update(gameTime, GraphicsDevice.Viewport.Bounds.Height);

            if (ball.Y < 0 || ball.Y > GraphicsDevice.Viewport.Bounds.Height - Assets.Ball.Height)
            {
                ballVelocity.Y = -ballVelocity.Y;
                Assets.BeepSound1.Play();
            }

            //player 2 scored
            if (ball.X < (0 - Assets.Ball.Width))
            {
                //ballVelocity.Y = -ballVelocity.Y;
                Assets.GoalSound.Play();

                //temp
                InitBall(new Vector2(-ballSpeed, 0));
                
                hud.UpdateScoreP2(10);
            }
            //player 1 scored
            else if (ball.X > GraphicsDevice.Viewport.Bounds.Width)
            {
                Assets.GoalSound.Play();

                //temp
                InitBall(new Vector2(ballSpeed, 0));

                hud.UpdateScoreP1(10);
            }     
         
            //check player1 bounds
            if (player1.X < 32)
                player1.X = 32;
            else if (player1.X > (GraphicsDevice.Viewport.Bounds.Width / 4) - player1.Width)
                player1.X = (GraphicsDevice.Viewport.Bounds.Width / 4) - player1.Width;

            //check player2 bounds
            if (player2.X < (GraphicsDevice.Viewport.Bounds.Width / 4) * 3)
                player2.X = (GraphicsDevice.Viewport.Bounds.Width / 4) * 3;
            else if (player2.X > (GraphicsDevice.Viewport.Bounds.Width) - (32 + player2.Width))
                player2.X = (GraphicsDevice.Viewport.Bounds.Width) - (32 + player2.Width);
            
            //if (ball.Intersects(player1.getRect()) || ball.Intersects(player2.getRect()))
            if (player1.Intersects(ball))
            {
                Vector2 normal = ComputeNormal(player1.UpperRightCorner(), player1.LowerRightCorner());
                ballVelocity = Vector2.Reflect(ballVelocity, normal);

                Matrix playerTransform = Matrix.CreateRotationZ(player1.Rotation) *
                    Matrix.CreateTranslation(new Vector3(player1.X, player1.Y, 0.0f));
                
                Matrix ballTransform = Matrix.CreateTranslation(new Vector3(ball.X + ballVelocity.X, ball.Y + ballVelocity.Y, 0.0f));

                contactPoints.AddRange(IntersectPixels(playerTransform, player1.Width,
                                       player1.Height, player1TextureData,
                                       ballTransform, ball.Width,
                                       ball.Height, ballTextureData));
                // Check collision with player
                if (contactPoints.Count != 0)
                {
                    collisionDetected = true;
                }
                else
                {
                    collisionDetected = false;
                }
                //Is this code nessesary?
                //ball.X += (int)ballVelocity.X;
                //ball.Y += (int)ballVelocity.Y;

                //collisionDetected = true;
                                               
            }
            else if (player2.Intersects(ball))
            {
                Vector2 normal = ComputeNormal(player2.UpperLeftCorner(), player2.LowerLeftCorner());
                ballVelocity = Vector2.Reflect(ballVelocity, normal);

                //Is this code nessesary?
                ball.X += (int)ballVelocity.X;
                ball.Y += (int)ballVelocity.Y;

                collisionDetected = true;
            }
            else
            {
                collisionDetected = false;
            }

            //update ball
            ball.X += (int)ballVelocity.X;
            ball.Y += (int)ballVelocity.Y;

            if (collisionDetected)
            {
                Assets.BeepSound2.Play();
            }

            if (player1.IsMoving)
            {
                if (Assets.MoveInstance.State != SoundState.Playing)
                {
                    Assets.MoveInstance.Play();
                }
            }
            else
            {
                Assets.MoveInstance.Stop();
            }            

            //if (player1.IsRotating)
            //{
            //    if (Assets.Move2Instance.State != SoundState.Playing)
            //    {
            //        Assets.Move2Instance.Play();
            //    }
            //}
            //else
            //{
            //    Assets.Move2Instance.Stop();
            //}


            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            // draw the grass background
            spriteBatch.Begin();
            //spriteBatch.Draw(Assets.Paddle, player1.getRect(), Color.Red);
            Rectangle aPositionAdjusted = new Rectangle(player1.X + (player1.Width / 2), player1.Y + (player1.Height / 2), player1.Width, player1.Height);
            if(player1.IsMoving)
                spriteBatch.Draw(Assets.Paddle, aPositionAdjusted, null, Color.Red, player1.Rotation, player1.Origin, SpriteEffects.None, 1);
            else if (player1.IsRotating)
                spriteBatch.Draw(Assets.Paddle, aPositionAdjusted, null, Color.Yellow, player1.Rotation, player1.Origin, SpriteEffects.None, 1);
            else
                spriteBatch.Draw(Assets.Paddle, aPositionAdjusted, null, Color.Red, player1.Rotation, player1.Origin, SpriteEffects.None, 1);



            aPositionAdjusted = new Rectangle(player2.X + (player2.Width / 2), player2.Y + (player2.Height / 2), player2.Width, player2.Height);
            spriteBatch.Draw(Assets.Paddle, aPositionAdjusted, null, Color.Blue, player2.Rotation, player2.Origin, SpriteEffects.FlipHorizontally, 1);
            
            aPositionAdjusted = new Rectangle(ball.X + (ball.Width / 2), ball.Y + (ball.Height / 2), ball.Width, ball.Height);
            if (collisionDetected)
            {
                spriteBatch.Draw(Assets.Ball, aPositionAdjusted, null, Color.Green, ball.Rotation, ball.Origin, SpriteEffects.None, 1);
            }
            else
            {
                spriteBatch.Draw(Assets.Ball, aPositionAdjusted, null, Color.White, ball.Rotation, ball.Origin, SpriteEffects.None, 1);
            }


            foreach (Vector2 p in contactPoints)
            {
                spriteBatch.Draw(Assets.Pixel, new Rectangle((int)p.X, (int)p.Y, 1, 1), Color.FromNonPremultiplied(120, 255, 100, 255));
            }


            //experiment
            Experiment.DrawCircle(spriteBatch);

            //draw hud
            hud.Draw(spriteBatch, GraphicsDevice.Viewport.Bounds.Width);

            spriteBatch.End();

            base.Draw(gameTime);
        }

        public void HandleInput(GameTime a_gameTime)
        {
            state = Keyboard.GetState();
            gamePadState = GamePad.GetState(PlayerIndex.One, GamePadDeadZone.Circular);

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                state.IsKeyDown(Keys.Escape))
                this.Exit();

            if (state.IsKeyDown(Keys.Space) || (gamePadState.IsButtonDown(Buttons.A) && oldGamePadState.IsButtonUp(Buttons.A)))
                InitBall(new Vector2(-ballSpeed, 0));
            else if (Mouse.GetState().LeftButton == ButtonState.Pressed)
            {
                Vector2 mouseVector = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
                mouseVector.Normalize();

                if (Mouse.GetState().X > GraphicsDevice.Viewport.Bounds.Width / 2)
                {
                    mouseVector.X *= 10;                    
                }
                else
                {
                    mouseVector.X = -mouseVector.X * 10;
                }
                if (Mouse.GetState().Y > GraphicsDevice.Viewport.Bounds.Height / 2)
                {
                    mouseVector.Y *= 10;
                }
                else
                {
                    mouseVector.Y = -mouseVector.Y * 10;
                }

                InitBall(mouseVector);
            }

            //right paddle
            if (state.IsKeyDown(Keys.NumPad5))
            {
                player2.ChangePosition(0,5);
            }
            else if (state.IsKeyDown(Keys.NumPad8))
            {
                player2.ChangePosition(0, -5);
            }

            if (state.IsKeyDown(Keys.NumPad9))// && state.IsKeyUp(Keys.Left))
            {
                player2.Rotate(0.1f, a_gameTime);
            }
            else if (state.IsKeyDown(Keys.NumPad7))// && state.IsKeyUp(Keys.Right))
            {
                player2.Rotate(-0.1f, a_gameTime);
            }

            if (state.IsKeyUp(Keys.NumPad7) && state.IsKeyUp(Keys.NumPad9))
            {
                player2.ResetRotation = true;
            }


            //left paddle
            //down
            if (state.IsKeyDown(Keys.S) || gamePadState.ThumbSticks.Left.Y < -THUMBSTICK_DEAD_ZONE)
            {
                player1.ChangePosition(0, 5);
                player1.IsMoving = true;
            }
            //up
            else if (state.IsKeyDown(Keys.W) || gamePadState.ThumbSticks.Left.Y > THUMBSTICK_DEAD_ZONE)
            {
                player1.ChangePosition(0, -5);
                player1.IsMoving = true;
            }
            //left            
            if (state.IsKeyDown(Keys.A) || gamePadState.ThumbSticks.Left.X < -THUMBSTICK_DEAD_ZONE)
            {
                player1.ChangePosition(-5, 0);
                player1.IsMoving = true;
            }
            //right
            else if (state.IsKeyDown(Keys.D) || gamePadState.ThumbSticks.Left.X > THUMBSTICK_DEAD_ZONE)
            {
                player1.ChangePosition(5, 0);
                player1.IsMoving = true;
            }
            else
            {
                player1.IsMoving = false;
            }

            if (state.IsKeyDown(Keys.E))
            {
                player1.Rotate(0.1f, a_gameTime);
            }
            else if (state.IsKeyDown(Keys.Q))
            {
                player1.Rotate(-0.1f, a_gameTime);
            }

            if (state.IsKeyUp(Keys.Q) && state.IsKeyUp(Keys.E))
            {
                player1.ResetRotation = true;
            }



            rightThumbStick = gamePadState.ThumbSticks.Right;
            //rightThumbStick.Normalize();

            if ((rightThumbStick.X > THUMBSTICK_DEAD_ZONE) || (rightThumbStick.X < -THUMBSTICK_DEAD_ZONE) ||
                (rightThumbStick.Y > THUMBSTICK_DEAD_ZONE) || (rightThumbStick.Y < -THUMBSTICK_DEAD_ZONE))
            {
                player1.Rotate(-rightThumbStick.Y);                             
            }
            //if (rightThumbStick.X > THUMBSTICK_DEAD_ZONE)
            //{
            //    float angle = RightThumbStickRotation(rightThumbStick, THUMBSTICK_DEAD_ZONE);
            //    Console.WriteLine(angle);
            //    player1.Rotation = angle;
            //}


            //rightThumbStick.Normalize();

            //float angle = (float)Math.Acos(rightThumbStick.Y);

            //if (rightThumbStick.X > THUMBSTICK_DEAD_ZONE)
            //{
            //    player1.Rotation = angle / 10;
            //}




            
            oldState = state;
            oldGamePadState = gamePadState;
        }

        public Vector2 ComputeNormal(Vector2 point1, Vector2 point2)
        {
            Vector2 normal = new Vector2();
            normal.X = point2.Y - point1.Y;
            normal.Y = point1.X - point2.X;

            normal.Normalize();

            return normal;
        }

        public float RightThumbStickRotation(Vector2 thumbStick, float deadZone) 
	    {
            thumbStick.Normalize();
            float angle = 0;
            if (((-deadZone > thumbStick.X) || (deadZone < thumbStick.X)) ||
                ((-deadZone > thumbStick.Y) || (deadZone < thumbStick.Y))) 
	        {
                if (thumbStick.Y > deadZone)
                {
                    Console.WriteLine("TESTING");
                    angle = (float)Math.Atan2(-thumbStick.X, -thumbStick.Y) / (float)Math.PI; 
                }
                else
                {
                    angle = (float)Math.Atan2(-thumbStick.X, -thumbStick.Y) / (float)Math.PI; 
                }
                
	        } 
	        return angle; 
	    } 

        public float Clerp(float from, float to, float step)
        {
            float t = ((MathHelper.WrapAngle(to - from) * (step)));
            return from + t;
        } 

        public void InitBall(Vector2 a_velocity)
        {
            Random rand = new Random();

            //switch (rand.Next(4))
            //{
            //    case 0: ballVelocity.X = ballSpeed; ballVelocity.Y = ballSpeed; break;
            //    case 1: ballVelocity.X = -ballSpeed; ballVelocity.Y = ballSpeed; break;
            //    case 2: ballVelocity.X = ballSpeed; ballVelocity.Y = -ballSpeed; break;
            //    case 3: ballVelocity.X = -ballSpeed; ballVelocity.Y = -ballSpeed; break;
            //}

            ballVelocity.X = a_velocity.X;
            ballVelocity.Y = a_velocity.Y;
            // initialize the ball to the center of the screen
            ball.X = GraphicsDevice.Viewport.Bounds.Width / 2 - ball.Width / 2;
            ball.Y = GraphicsDevice.Viewport.Bounds.Height / 2 - ball.Height / 2;
        }

        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels
        /// between two sprites.
        /// </summary>
        /// <param name="rectangleA">Bounding rectangle of the first sprite</param>
        /// <param name="dataA">Pixel data of the first sprite</param>
        /// <param name="rectangleB">Bouding rectangle of the second sprite</param>
        /// <param name="dataB">Pixel data of the second sprite</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        public static bool IntersectPixels(Rectangle rectangleA, Color[] dataA,
                                           Rectangle rectangleB, Color[] dataB)
        {
            // Find the bounds of the rectangle intersection
            int top = Math.Max(rectangleA.Top, rectangleB.Top);
            int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
            int left = Math.Max(rectangleA.Left, rectangleB.Left);
            int right = Math.Min(rectangleA.Right, rectangleB.Right);

            // Check every point within the intersection bounds
            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = dataA[(x - rectangleA.Left) +
                                         (y - rectangleA.Top) * rectangleA.Width];
                    Color colorB = dataB[(x - rectangleB.Left) +
                                         (y - rectangleB.Top) * rectangleB.Width];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0 && colorB.A != 0)
                    {
                        // then an intersection has been found
                        return true;
                    }
                }
            }

            // No intersection found
            return false;
        }

        public static IEnumerable<Vector2> IntersectPixels(
                    Matrix transformA, int widthA, int heightA, Color[] dataA,
                    Matrix transformB, int widthB, int heightB, Color[] dataB)
        {
            // Calculate a matrix which transforms from A's local space into
            // world space and then into B's local space
            Matrix transformAToB = transformA * Matrix.Invert(transformB);

            // When a point moves in A's local space, it moves in B's local space with a
            // fixed direction and distance proportional to the movement in A.
            // This algorithm steps through A one pixel at a time along A's X and Y axes
            // Calculate the analogous steps in B:
            Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
            Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);

            // Calculate the top left corner of A in B's local space
            // This variable will be reused to keep track of the start of each row
            Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);

            // For each row of pixels in A
            for (int yA = 0; yA < heightA; yA++)
            {
                // Start at the beginning of the row
                Vector2 posInB = yPosInB;

                // For each pixel in this row
                for (int xA = 0; xA < widthA; xA++)
                {
                    // Round to the nearest pixel
                    int xB = (int)Math.Round(posInB.X);
                    int yB = (int)Math.Round(posInB.Y);

                    // If the pixel lies within the bounds of B
                    if (0 <= xB && xB < widthB &&
                        0 <= yB && yB < heightB)
                    {
                        // Get the colors of the overlapping pixels
                        Color colorA = dataA[xA + yA * widthA];
                        Color colorB = dataB[xB + yB * widthB];

                        // If both pixels are not completely transparent,
                        if (colorA.A != 0 && colorB.A != 0)
                        {
                            // then an intersection has been found
                            yield return Vector2.Transform(new Vector2(xA, yA), transformA);
                        }
                    }

                    // Move to the next pixel in the row
                    posInB += stepX;
                }

                // Move to the next row
                yPosInB += stepY;
            }

            // No intersection found
        }
    }
}
